package gateway.mgt.api

import com.amihaiemil.camel.Yaml
import com.amihaiemil.camel.YamlSequenceBuilder
import gateway.mgt.api.ratelimit.RateLimitStrategy
import grails.gorm.dirty.checking.DirtyCheck
import grails.validation.Validateable
import groovy.transform.ToString
import org.springframework.http.HttpMethod

//import grails.rest.Resource
import org.springframework.http.HttpStatus

//@Resource(uri = "/apiDefinition")
@ToString(includes = ["name", "request", "backend"], includePackage = false)
class ApiDefinition implements Comparable<ApiDefinition> {

    static enum ReuqestProtocol {
        HTTP, HTTPS, HTTP_HTTPS
    }

    static enum ContentType {
        JSON, XML
    }

    @DirtyCheck
    @ToString(includePackage = false, includes = ["method", "path"])
    static class Request implements Validateable {

        ReuqestProtocol protocol = ReuqestProtocol.HTTP_HTTPS
        String path
        HttpMethod method = HttpMethod.GET

        static constraints = {
            protocol(nullable: false)
            path(blank: false)
            method(nullable: false)
        }
    }

    @DirtyCheck
    @ToString(includePackage = false, includes = ["uri", "path"])
    static class BackendService implements Validateable {
        String uri
        String path
        HttpMethod method = HttpMethod.GET
        long timeout = 6000

        static constraints = {
            uri(blank: false)
            path(blank: true)
            method(nullable: false)
            timeout(range: 0..10000)
        }
    }

    @DirtyCheck
    @ToString(includePackage = false, includes = ["type"])
    static class ResponseResult implements Validateable {
        ContentType type = ContentType.JSON
        HttpStatus faultStatus = HttpStatus.SERVICE_UNAVAILABLE
        String faultContent

        static constraints = {
            type(nullable: false)
            faultStatus(nullable: false)
            faultContent(nullable: true)
        }

        static mapping = {
            faultContent type: "text"
        }
    }

    ApiGroup apiGroup
    String apiGroupName
    String name
    String description

    Request request
    BackendService backend
    ResponseResult result

    Date dateCreated
    Date lastUpdated

    static embedded = ['request', 'backend', "result"]

    static belongsTo = [ApiAuth, RateLimitStrategy]
    static hasMany = [inOutParams: ApiInOutParam, constantParams: ApiConstantParam, systemParams: ApiSystemParam, apiAuths: ApiAuth, rateLimitStrategies: RateLimitStrategy]

    static constraints = {
        name(blank: false, unique: "apiGroupName")
        apiGroup(nullable: false)
        apiGroupName(nullable: false, display: false)
        request(nullable: false)
        backend(nullable: false)
        result(nullable: true)
        inOutParams(nullable: true)
        constantParams(nullable: true)
        systemParams(nullable: true)
        description(nullable: true, maxSize: 200, widget: 'textarea')
        apiAuths(nullable: true)
        rateLimitStrategies(nullable: true)
        dateCreated()
        lastUpdated()
    }

    def beforeValidate() {
        apiGroupName = apiGroup?.name
    }

    @Override
    int compareTo(ApiDefinition o) {
        id <=> o.id
    }

    void route(final Map groupBuildElements) {
        def route = Yaml.createYamlMappingBuilder()
                .add("id", name)
                .add("uri", "$backend.uri")

        final routeElements = [
                filters   : Yaml.createYamlSequenceBuilder(),
                predicates: Yaml.createYamlSequenceBuilder().add("Path=$request.path")
        ]

        [inOutParams, constantParams, systemParams, rateLimitStrategies].each { params ->
            params.each { Pluggable p -> p.plug(routeElements, this) }
        }

        routeElements.filters = routeElements.filters.add(Yaml.createYamlMappingBuilder()
                .add("name", "Hystrix")
                .add("args", Yaml.createYamlMappingBuilder().add("name", name).add("fallbackUri", "forward:/hystrixfallback").build())
                .build()
        )

        def filters = routeElements.filters.build(), predicates = routeElements.predicates.build()

        route = route.add("predicates", predicates)
        route = route.add("filters", filters)

        groupBuildElements.routes = groupBuildElements.routes.add(route.build())

        groupBuildElements.hystrix = groupBuildElements.hystrix.add(name,
                Yaml.createYamlMappingBuilder()
                        .add("execution",
                        Yaml.createYamlMappingBuilder()
                                .add("isolation",
                                Yaml.createYamlMappingBuilder()
                                        .add("thread",
                                        Yaml.createYamlMappingBuilder()
                                                .add("timeoutInMilliseconds", backend.timeout as String)
                                                .build()
                                ).build()
                        ).build()
                ).build()
        )
    }

    interface Pluggable {
        void plug(final Map<String, YamlSequenceBuilder> routeBuildElements, final ApiDefinition current)
    }
}
